/* 
 * File:   Enemy.cpp
 * Author: paula
 * 
 * Created on 7 de noviembre de 2014, 14:27
 */

#include "Enemy.h"

Enemy::Enemy() {
}

Enemy::Enemy(ObjFactory *objFactory, int x, int y, int id) {

    this->myLogFile->getInstance(MY_LOG_MODE);
    this->objFactory = objFactory;
    this->windowUnitWidth = this->objFactory->getUnitWidth();
    this->windowUnitHeight = this->objFactory->getUnitHeight();
    this->objWidth = 100;
    this->objHeight = 100;
    Dot *dotito = new Dot(x, y, objWidth, objHeight);
    string lineToLog = "";

    this->dot = dotito;
    Object *object = objFactory->getCharacter(this->dot->dotPixelsToWorld(), this->dot->sizeDotPixelsToWorld());
    this->object = object;

    b2Body* body = this->object->getBody();
    b2Fixture* fixt = body->GetFixtureList();
    if (fixt->IsSensor()) {
        fixt = fixt->GetNext();
    }
    b2Filter filter = fixt->GetFilterData();
    filter.groupIndex = GROUPINDEXENEMY;
    fixt->SetFilterData(filter);

    this->directionRight = true;
    this->counterEscaping = 0;
    this->counterSnow = 0;
    this->counterRight = 0;
    this->counterLeft = 0;
    this->counterKneel = 1;
    this->counterJump = 1;
    this->counterWait = 9;
    this->beginning = true;
    this->delayWalk = 10;
    this->delay = 20;
    this->jumping = false;
    this->kneeling = false;
    this->walking = true;
    this->fallingDown = false;
    this->wasJumping = false;
    this->caught = false;
    this->escaping = false;
    this->dead = false;
    this->dying = false;
    this->lastPosY = 0;
    this->currentPosY = 0;
    this->lastVelY = 0;
    this->currentVelY = 0;
    this->kicked = false;
    this->alive = true;

    // inicializo data 
    struct ObjectInit obj;
    obj.x = x;
    obj.y = y;
    obj.stateCharacter = WAITING;
    obj.estatico = false;
    obj.event.pressed = 'Z'; // inicial defaul
    obj.objectId = VACIO;
    this->data = obj;

    this->equals = false;
    this->wasJumping = false;

    this->velocityWalkLeft = 0.0;
    this->velocityWalkRight = 0.0;

    this->setCharacterId(id);

    this->velocityWalkLeft = 0.0;
    this->velocityWalkRight = 0.0;
    this->maxVelocity = 4.0;
    this->deltaVelocity = 0.5;

    this->posAntX = 0;
    this->timeUncaught = 2; //seconds

    //shoot                    
    //        body->GetFixtureList()->SetUserData((void*) IS_ENEMY);
    b2Fixture* fixtt = body->GetFixtureList();
    fixtt->SetUserData((void*) IS_ENEMY);
    fixtt = fixtt->GetNext();
    fixtt->SetUserData((void*) IS_ENEMY);

    //temporal, a eliminar casa de LU
    SnowGun *weapon = new SnowGun(objFactory);
    this->weapon = weapon;

    this->isACrazyBall = false;
    this->timeToDisappear = 5;

    this->posToBonusX = 0;
    this->posToBonusY = 0;
    this->posDisappear = -200;
}

Enemy::Enemy(const Enemy& orig) {
}

int Enemy::getCantCont() {

    b2Body* body = this->object->getBody();
    int cantCont = 0;
    for (b2ContactEdge* contList = body->GetContactList(); contList; contList = contList->next) {
        b2Contact* cont = contList->contact;
        b2Fixture* fixtA = cont->GetFixtureA();
        b2Fixture* fixtB = cont->GetFixtureB();
        if ((!fixtA->IsSensor()) && (!fixtB->IsSensor())) {
            cantCont++;
        }
    }
    return cantCont;
}

void Enemy::keepWalking() {

    if (this->directionRight) {
        if (velocityWalkRight < maxVelocity) {
            velocityWalkRight = velocityWalkRight + deltaVelocity;
        }
        this->moveRight(velocityWalkRight, 0.0f);
        this->actualizePosition();
    } else {
        if (velocityWalkLeft < maxVelocity) {
            velocityWalkLeft = velocityWalkLeft + deltaVelocity;
        }
        this->moveLeft(velocityWalkLeft, 0.0f);
        this->actualizePosition();
    }
}

void Enemy::walk() {

    float deltaPosX = (this->posAntX - this->dot->getPos()->x);
    this->posAntX = this->dot->getPos()->x;
    switch (this->getState()) {
        case JUMPING:
            this->keepJumping();
            break;
        case KNEELING:
            this->keepKneeling();
            break;
        case FALLING:
            this->keepFallingDown();
            break;
        case WALKING:
            this->keepWalking();

            if (this->isWalkingLeft()) {
                this->counterLeft++;
                if (this->counterLeft > 1000) {
                    this->counterLeft = 1000;
                }
            }

            if (this->isWalkingRight()) {
                this->counterRight++;
                if (this->counterRight > 1000) {
                    this->counterRight = 1000;
                }
            }

            if (this->isWalkingRight() && (deltaPosX == 0) && (this->counterRight > 20)) {
                this->walkLeft();
                this->counterLeft = 0;
                break;
            } else {
                if (this->isWalkingLeft() && (deltaPosX == 0) && (this->counterLeft > 20)) {
                    this->walkRight();
                    this->counterRight = 0;
                    break;
                }
            }

            //NOTA: Agregar
            this->jumpRandom();
            break;
        case WAITING:
            this->jumpRandom();
            break;
        case ESCAPING:
            this->escape();
            break;
        case CAUGHT:

            if (this->counterSnow == 5) {

                // El character reconoce que esta atrapado 'del todo'
                b2Fixture* fixtt = this->object->getBody()->GetFixtureList();
                fixtt->SetUserData((void*) IS_ENEMY_CAUGHT);
                fixtt = fixtt->GetNext();
                fixtt->SetUserData((void*) IS_ENEMY_CAUGHT);

                // para que el character pueda colisionar
                b2Fixture* fixt = this->object->getBody()->GetFixtureList();
                if (fixt->IsSensor()) {
                    fixt = fixt->GetNext();
                }
                b2Filter filter = fixt->GetFilterData();
                filter.groupIndex = GROUPINDEXENEMY;
                fixt->SetFilterData(filter);

            } else if ((this->counterSnow > 0) && (this->counterSnow < 5)) {
                // el character no puede morir si esta atrapado
                b2Fixture* fixt = this->object->getBody()->GetFixtureList();

                if (fixt->IsSensor()) {
                    fixt = fixt->GetNext();
                }
                b2Filter filter = fixt->GetFilterData();
                filter.groupIndex = GROUPINDEXCHARACTER;
                fixt->SetFilterData(filter);

                b2Fixture* fixtt = this->object->getBody()->GetFixtureList();
                fixtt->SetUserData((void*) IS_ENEMY);
                fixtt = fixtt->GetNext();
                fixtt->SetUserData((void*) IS_ENEMY);
                //                    cout << "enemy free"<<endl;

            }

            this->cronoNow = std::chrono::system_clock::now();
            int elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>
                    (this->cronoNow - this->cronoStart).count();
            if ((elapsed_seconds > this->timeUncaught) && (!this->kicked)) {
                this->escaping = true;
                this->caught = false;
            }

            this->data.enemyStat = this->counterSnow;

            if (this->counterSnow == 0) {
                // para que el character pueda colisionar de nuevo
                b2Fixture* fixt = this->object->getBody()->GetFixtureList();
                if (fixt->IsSensor()) {
                    fixt = fixt->GetNext();
                }
                b2Filter filter = fixt->GetFilterData();
                filter.groupIndex = GROUPINDEXENEMY;
                fixt->SetFilterData(filter);
            }
            break;

    }

}

void Enemy::beCaught() {
    //    this->counterEscaping = 0;
    this->cronoStart = std::chrono::system_clock::now();
    if ((!this->escaping) && (this->counterSnow < 5)) {
        this->counterSnow++;
    }

    this->walking = false;
    this->kneeling = false;
    this->jumping = false;
    this->fallingDown = false;
    this->caught = true;
    this->data.enemyStat = this->counterSnow;

    if ((this->counterSnow > 0) && (this->counterSnow < 5)) {
        // el character no puede morir si esta atrapado
        b2Fixture* fixt = this->object->getBody()->GetFixtureList();

        if (fixt->IsSensor()) {
            fixt = fixt->GetNext();
        }
        b2Filter filter = fixt->GetFilterData();
        filter.groupIndex = GROUPINDEXCHARACTER;
        fixt->SetFilterData(filter);

    }
}

void Enemy::escape() {
    this->caught = false;
    this->counterEscaping++;
    this->data.enemyStat = (this->counterSnow - 1);
    if (this->counterEscaping == 3) {
        this->counterEscaping = 0;
        this->counterSnow--;
        this->escaping = false;
        this->caught = true;
        this->cronoStart = std::chrono::system_clock::now();
        if (this->counterSnow == 0) {
            this->caught = false;
            this->escaping = false;
            this->walking = true;
        }
    }

    if (this->counterSnow == 0) {
        // para que el character pueda colisionar de nuevo
        b2Fixture* fixt = this->object->getBody()->GetFixtureList();
        if (fixt->IsSensor()) {
            fixt = fixt->GetNext();
        }

        b2Filter filter = fixt->GetFilterData();
        filter.groupIndex = GROUPINDEXENEMY;
        fixt->SetFilterData(filter);
    }

}

void Enemy::updateData() {
    this->updatePosition();
    this->updateState();
}

void Enemy::updateState() {
    this->data.stateCharacter = this->getState();
    this->data.direction = this->directionRight;
}

int Enemy::getState() {
    if (this->caught) {
        return CAUGHT;
    }
    if (isJumping()) {
        return JUMPING;
    }
    if (isKneeling()) {
        return KNEELING;
    }
    if ((!caught) && isFallingDown()) {

        b2Fixture* fixt = this->object->getBody()->GetFixtureList();
        if (fixt->IsSensor()) {
            fixt = fixt->GetNext();
        }
        b2Filter filter = fixt->GetFilterData();
        filter.groupIndex = GROUPINDEXENEMY;
        fixt->SetFilterData(filter);

        return FALLING;
    }
    if (isWalking()) {
        return WALKING;
    }
    if (this->escaping) {
        return ESCAPING;
    } else {
        return WAITING;
    }
}

bool Enemy::getAreEquals() {
    if (this->caught || this->escaping) {
        return false;
    }
    if (this->equals && (this->lastState == this->data.stateCharacter)) {
        return true;
    } else {
        this->lastState = this->data.stateCharacter;
        return false;
    }
    return false;
}

void Enemy::checkChanges() {
    b2Vec2 pos = this->object->getPosition();
    if (this->data.x == pos.x && this->data.y == pos.y) {
        this->equals = true;
    } else {
        this->equals = false;
    }
}

void Enemy::checkContacts() {
    b2Body* body = this->getBody();
    for (b2ContactEdge* contList = body->GetContactList(); contList; contList = contList->next) {
        b2Contact* cont = contList->contact;
        b2Fixture* fixtA = cont->GetFixtureA();
        b2Fixture* fixtB = cont->GetFixtureB();
        void* dataVoidA = fixtA->GetUserData();

        int data = *((int*) &dataVoidA);
        // TODO: mejorar esto.. ver si cambia la imagen..
        if ((data == IS_ENEMY) || (data == IS_ENEMY_CAUGHT) || (data == IS_CHARACTER)) {
            struct ObjectInit ob = this->getData();
            ob.imageId = 2;



            if (data == IS_CHARACTER) {
                //                b2Body* body = fixtA->GetBody();
                //                body->SetType(b2_staticBody);
                //                Dot *dot = new Dot(this->posDisappear, this->posDisappear);
                //                dot->dotPixelsToWorld();
                //                b2Vec2* pos = dot->getPos();                
                //                body->SetTransform(*pos, 0);                
                //                this->charactersTrapped.push_back(body);
//                cout << "crazy ball in contaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccttttttttt" << endl;
            }

        }

        void* dataVoidB = fixtB->GetUserData();
        data = *((int*) &dataVoidB);
        // TODO: mejorar esto.. ver si cambia la imagen..
        if ((data == IS_ENEMY) || (data == IS_ENEMY_CAUGHT) || (data == IS_CHARACTER)) {
            struct ObjectInit ob = this->getData();
            ob.imageId = 2;

            if (data == IS_CHARACTER) {
                //                b2Body* body = fixtA->GetBody();
                //                body->SetType(b2_staticBody);
                //                Dot *dot = new Dot(this->posDisappear, this->posDisappear);
                //                dot->dotPixelsToWorld();
                //                b2Vec2* pos = dot->getPos();                
                //                body->SetTransform(*pos, 0);                
                //                this->charactersTrapped.push_back(body);
//                cout << "crazy ball in contaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccttttttttt" << endl;
            }

        }
    }

}

//shoot

bool Enemy::inConctactBullet() {
    bool contacBullet = false;
    b2Body* body = this->getBody();
    for (b2ContactEdge* contList = body->GetContactList(); contList; contList = contList->next) {
        b2Contact* cont = contList->contact;
        b2Fixture* fixtA = cont->GetFixtureA();
        b2Fixture* fixtB = cont->GetFixtureB();
        void* dataVoidA = fixtA->GetUserData();
        int data = *((int*) &dataVoidA);
        if (data == IS_BULLET) { //contacto con una bala.            
            contacBullet = true;
        }
        void* dataVoidB = fixtB->GetUserData();
        data = *((int*) &dataVoidB);
        if (data == IS_BULLET) { //contacto con una bala.                        
            contacBullet = true;
        }
    }
    return contacBullet;
}

void Enemy::jumpRandom() {
    int randomNumber = rand() % 500;
    if (randomNumber == 0) {
        this->jump();
    }
    if (randomNumber == 1) {
        this->wait();
    }
    if (randomNumber == 2) {
        this->walkRight();
    }
    if (randomNumber == 3) {
        this->walkLeft();
    }
}

bool Enemy::inConctactCrazyBall() {

    b2Body* body = this->getBody();
    for (b2ContactEdge* contList = body->GetContactList(); contList; contList = contList->next) {
        b2Contact* cont = contList->contact;
        b2Fixture* fixtA = cont->GetFixtureA();
        b2Fixture* fixtB = cont->GetFixtureB();
        void* dataVoidA = fixtA->GetUserData();
        void* dataVoidB = fixtB->GetUserData();

        int dataA = *((int*) &dataVoidA);
        int dataB = *((int*) &dataVoidB);


        b2Filter filterA = fixtA->GetFilterData();

        b2Filter filterB = fixtB->GetFilterData();

//        if (this->isACrazyBall) {
//            cout << "soy crazy ball" << endl;
//        }

        //                if (dataA != IS_FLOOR)
        //                    cout << "data A " << dataA << "   data B " << dataB << endl;

        //        if ((dataA == IS_CRAZY_BALL) &&(filterA.groupIndex == GROUPINDEXCRAZYBALL)) {
        //            return true;
        //        }
        //
        //
        //        if ((dataB == IS_CRAZY_BALL) &&(filterB.groupIndex == GROUPINDEXCRAZYBALL)) {
        //            return true;
        //        }

        // si no soy una crazy ball, y estoy en contacto con una
        if ((dataA == IS_CRAZY_BALL) && (!this->isACrazyBall)) {
            //        if ((filterA.groupIndex == GROUPINDEXCRAZYBALL) && (!this->isACrazyBall)){
            return true;
        }

        // si no soy una crazy ball, y estoy en contacto con una
        if ((dataB == IS_CRAZY_BALL) && (!this->isACrazyBall)) {
            //        if ((filterB.groupIndex == GROUPINDEXCRAZYBALL) && (!this->isACrazyBall)){
            return true;
        }
    }
    return false;
}

bool Enemy::toKick() {
    void* dataVoid = this->getBody()->GetUserData();
    int userData = *((int*) &dataVoid);
    if (userData == TO_KICK) {
        b2Body* body = this->getBody();
        for (b2ContactEdge* contList = body->GetContactList(); contList; contList = contList->next) {
            b2Contact* cont = contList->contact;
            b2Fixture* fixtA = cont->GetFixtureA();
            b2Fixture* fixtB = cont->GetFixtureB();
            void* dataVoidA = fixtA->GetUserData();

            int data = *((int*) &dataVoidA);
            // TODO: mejorar esto
            if (data != IS_FLOOR && data != IS_ENEMY && data != IS_ENEMY_CAUGHT) {
                this->kicked = true;
                if (fixtA->GetBody()->GetPosition().x > body->GetPosition().x) {
                    // si el que me patea esta en mayor X, esta a mi derecha
                    this->kickedFromRight = true;
                } else {
                    this->kickedFromRight = false;
                }

                return true;
            }

            void* dataVoidB = fixtB->GetUserData();
            data = *((int*) &dataVoidB);
            // TODO: mejorar esto
            if (data != IS_FLOOR && data != IS_ENEMY && data != IS_ENEMY_CAUGHT) {
                this->kicked = true;
                if (fixtB->GetBody()->GetPosition().x > body->GetPosition().x) {
                    // si el que me patea esta en mayor X, esta a mi derecha
                    this->kickedFromRight = true;
                } else {
                    this->kickedFromRight = false;
                }

                return true;
            }
        }
    }

    return false;

}

bool Enemy::toDie() {
    if (!this->dead) {
        void* dataVoid = this->getBody()->GetUserData();
        int userData = *((int*) &dataVoid);
        if (userData == TO_DELETE) {
            return true;
        }
    }
    return false;
}

void Enemy::setCrazyBall() {

    if (!this->isACrazyBall) {
        b2Body* body = this->getBody();

        float inpulseX = 1000.5f;
        float inpulseY = -100.0f;

        //datos para setear un impulso. Izquierda o derecha 
        b2Vec2 impulse(0.0f, 0.0f);
        if (this->kickedFromRight) {
            float var = inpulseX * (-1);
            impulse.Set(var, inpulseY);
        } else {
            impulse.Set(inpulseX, inpulseY);
        }

        this->cronoStartDead = std::chrono::system_clock::now();

        b2Vec2 pos(body->GetWorldCenter().x, body->GetWorldCenter().y);
        body->ApplyLinearImpulse(impulse, pos, true);

        // SETEO GROUPINDEX PARA QUE RECONOZCA LOS OTROS ENEMIES

        b2Fixture* fixtt = this->object->getBody()->GetFixtureList();
        //        fixtt->SetRestitution(0.5);
        b2Filter filter = fixtt->GetFilterData();
        filter.groupIndex = GROUPINDEXCRAZYBALL;
        fixtt->SetFilterData(filter);
        fixtt->SetUserData((void*) IS_CRAZY_BALL);

        fixtt = fixtt->GetNext();
        //        fixtt->SetRestitution(0.5);
        filter = fixtt->GetFilterData();
        filter.groupIndex = GROUPINDEXCRAZYBALL;
        fixtt->SetFilterData(filter);
        fixtt->SetUserData((void*) IS_CRAZY_BALL);

        this->isACrazyBall = true;
    }

}

bool Enemy::isCrazyBall() {
    return this->isACrazyBall;
}

bool Enemy::tryToDelete() {
    if (this->isACrazyBall) {
        this->cronoNowDead = std::chrono::system_clock::now();
        int elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>
                (this->cronoNowDead - this->cronoStartDead).count();
        if (elapsed_seconds > this->timeToDisappear) {

            //            this->killMyContactsEnemies();

            Dot *unDot = new Dot(this->getPosX(), getPosY());
            unDot->dotWorldToPixels();
            this->posToBonusX = unDot->getPos()->x;
            this->posToBonusY = unDot->getPos()->y;
            this->disappear();

            // traigo de nuevo a los players al juego
            for (int i = 0; i< this->charactersTrapped.size(); i++) {
                // si la posicion no es la seteada, el character ya se habia escapado
                if (this->charactersTrapped.at(i)->GetPosition().x != this->posDisappear) {
                    this->charactersTrapped.at(i)->SetType(b2_dynamicBody);
                    this->charactersTrapped.at(i)->SetTransform(*unDot->getPos(), 0);
                }
            }

            // reinicio el vector de players para proxima crazy ball
            int tam = this->charactersTrapped.size();
            for (int i = 0; i < tam; i++) {
                this->charactersTrapped.pop_back();
            }

            this->isACrazyBall = false;

            delete unDot;
            return true;
        }
        return false;
    }
    return false;
}

void Enemy::killMyContactsEnemies() {
    b2Body* body = this->getBody();
    for (b2ContactEdge* contList = body->GetContactList(); contList; contList = contList->next) {
        b2Contact* cont = contList->contact;
        b2Fixture* fixtA = cont->GetFixtureA();
        b2Fixture* fixtB = cont->GetFixtureB();
        void* dataVoidA = fixtA->GetUserData();

        int data = *((int*) &dataVoidA);
        // TODO: mejorar esto
        if ((data == IS_ENEMY) || (data == IS_ENEMY_CAUGHT)) {
            fixtA->GetBody()->SetUserData((void*) TO_DELETE);

//            cout << "enemy to killlllllllllllllllllllllllllllllllllllllll" << endl;
        }

        void* dataVoidB = fixtB->GetUserData();
        data = *((int*) &dataVoidB);
        // TODO: mejorar esto
        if ((data == IS_ENEMY) || (data == IS_ENEMY_CAUGHT)) {
            fixtB->GetBody()->SetUserData((void*) TO_DELETE);
//            cout << "enemy to killlllllllllllllllllllllllllllllllllllllll" << endl;
        }
    }
}

void Enemy::kill() {
    //    this->data.imageId = -1;
    this->disappear();
}

void Enemy::disappear() {
    if (!this->dead) {
        this->changePosition(-100, -100);
        this->object->getBody()->SetType(b2_staticBody);
        this->caught = false;
        this->escaping = false;
        this->alive = false;

        this->dead = true;
    }
}

bool Enemy::isCaught() {
    return this->caught;
}

void Enemy::changePosition(float x, float y) {
    Dot *dot = new Dot(x, y);
    dot->dotPixelsToWorld();
    b2Vec2* pos = dot->getPos();
    this->object->getBody()->SetTransform(*pos, 0);
    delete dot;
}

float Enemy::getPosToBonusX() {
    return this->posToBonusX;
}

float Enemy::getPosToBonusY() {
    return this->posToBonusY;
}

Enemy::~Enemy() {
}
